Оценка результатов A/B-теста, связанных с внедрением улучшенной рекомендательной системы в интернет-магазине
</font>

Описание данных и задачи¶

**Постановка задачи**

Ваша задача — провести оценку результатов A/B-теста. В вашем распоряжении есть датасет с действиями пользователей, техническое задание и несколько вспомогательных датасетов.

  • Оцените корректность проведения теста
  • Проанализируйте результаты теста

Чтобы оценить корректность проведения теста, проверьте:

  • пересечение тестовой аудитории с конкурирующим тестом,
  • совпадение теста и маркетинговых событий, другие проблемы временных границ теста.

**Техническое задание**

  • Название теста: recommender_system_test;
  • группы: А — контрольная, B — новая платёжная воронка;
  • дата запуска: 2020-12-07;
  • дата остановки набора новых пользователей: 2020-12-21;
  • дата остановки: 2021-01-04;
  • аудитория: 15% новых пользователей из региона EU;
  • назначение теста: тестирование изменений, связанных с внедрением улучшенной рекомендательной системы;
  • ожидаемое количество участников теста: 6000.
  • ожидаемый эффект: за 14 дней с момента регистрации пользователи покажут улучшение каждой метрики не менее, чем на 10%:
    • конверсии в просмотр карточек товаров — событие product_page,
    • просмотры корзины — product_cart,
    • покупки — purchase.

**Данные**

final_ab_events.csv

ab_project_marketing_events.csv

final_ab_new_users.csv

final_ab_participants.csv

**Описание данных**

ab_project_marketing_events.csv — календарь маркетинговых событий на 2020 год.

Структура файла:

  • name — название маркетингового события;
  • regions — регионы, в которых будет проводиться рекламная кампания;
  • start_dt — дата начала кампании;
  • finish_dt — дата завершения кампании.

final_ab_new_users.csv — пользователи, зарегистрировавшиеся с 7 по 21 декабря 2020 года.

Структура файла:

  • user_id — идентификатор пользователя;
  • first_date — дата регистрации;
  • region — регион пользователя;
  • device — устройство, с которого происходила регистрация.

final_ab_events.csv — действия новых пользователей в период с 7 декабря 2020 по 4 января 2021 года.

Структура файла:

  • user_id — идентификатор пользователя;
  • event_dt — дата и время покупки;
  • event_name — тип события;
  • details — дополнительные данные о событии. Например, для покупок, purchase, в этом поле хранится стоимость покупки в долларах.

final_ab_participants.csv — таблица участников тестов.

Структура файла:

  • user_id — идентификатор пользователя;
  • ab_test — название теста;
  • group — группа пользователя.

**Оглавление**

  • 1. Изучение входных данных
  • 2. Предобработка данных
    • 2.1. Проверка типов данных
    • 2.2. Проверим дубликаты и пропуски
  • 3. Анализ данных (EDA)
    • 3.1. Оценка корректности проведения теста
    • 3.2. Распределение количества событий на пользователя в выборках
    • 3.3. Распределение количество событий по дням
    • 3.4. Изменение конверсии воронки в выборках на разных эатапах
    • 3.5. Особенности данных перед А/В-тестирование
  • 4. Оценка результатов А/В-тестирования

    • 4.1. Результаты А/В-тестирования
    • 4.2. Проверка статистической разницы долей z-критериев
  • 5. Общий вывод по этапу исследовательского анализа данных и по проведённой оценке результатов A/B-тестирования. Заключение о корректности проведения теста

Примечание:

  • Оцените корректность проведения теста. Обратите внимание на:
    • Соответствие данных требованиям технического задания. Проверьте корректность всех пунктов технического задания.
    • Время проведения теста. Убедитесь, что оно не совпадает с маркетинговыми и другими активностями.
    • Аудиторию теста. Удостоверьтесь, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно. Проверьте равномерность распределения по тестовым группам и правильность их формирования.

Выполнение проекта¶

Изучение входных данных¶

In [1]:
# Загружаем библиотеки
import pandas as pd
from urllib.parse import urlencode
import requests
import numpy as np
import seaborn as sns
import datetime as dt
from scipy import stats as st
import math as mth
from datetime import date, datetime, timedelta
from matplotlib import pyplot as plt
from plotly import graph_objects as go
import plotly.express as px
import warnings
warnings.filterwarnings('ignore')

Откроем файлы¶

In [2]:
#Напишем функцию

def datasets (base_url, public_key):
    final_url = base_url + urlencode(dict(public_key=public_key))
    response = requests.get(final_url)
    download_url = response.json()['href']
    dataset = pd.read_csv(download_url)
    return dataset
In [3]:
#Выведим данные таблицы final_ab_participants
base_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?'
public_key = 'https://disk.yandex.ru/d/nhGUR3ZkNIG4VA' 

final_ab_participants  = datasets (base_url, public_key)
final_ab_participants
Out[3]:
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test
... ... ... ...
18263 1D302F8688B91781 B interface_eu_test
18264 3DE51B726983B657 A interface_eu_test
18265 F501F79D332BE86C A interface_eu_test
18266 63FBE257B05F2245 A interface_eu_test
18267 79F9ABFB029CF724 B interface_eu_test

18268 rows × 3 columns

In [4]:
#Выведим данные таблицы final_ab_events
base_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?'
public_key = 'https://disk.yandex.ru/d/pgKgZ9lRp0Enwg'

final_ab_events  = datasets (base_url, public_key)
final_ab_events
Out[4]:
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99
... ... ... ... ...
440312 245E85F65C358E08 2020-12-30 19:35:55 login NaN
440313 9385A108F5A0A7A7 2020-12-30 10:54:15 login NaN
440314 DB650B7559AC6EAC 2020-12-30 10:59:09 login NaN
440315 F80C9BDDEA02E53C 2020-12-30 09:53:39 login NaN
440316 7AEC61159B672CC5 2020-12-30 11:36:13 login NaN

440317 rows × 4 columns

In [5]:
#Выведим данные таблицы ab_project_marketing_events
base_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?'
public_key = 'https://disk.yandex.ru/d/ZVC7GUYYckKnGQ'

ab_project_marketing_events  = datasets (base_url, public_key)
ab_project_marketing_events
Out[5]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
5 Black Friday Ads Campaign EU, CIS, APAC, N.America 2020-11-26 2020-12-01
6 Chinese New Year Promo APAC 2020-01-25 2020-02-07
7 Labor day (May 1st) Ads Campaign EU, CIS, APAC 2020-05-01 2020-05-03
8 International Women's Day Promo EU, CIS, APAC 2020-03-08 2020-03-10
9 Victory Day CIS (May 9th) Event CIS 2020-05-09 2020-05-11
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07
11 Dragon Boat Festival Giveaway APAC 2020-06-25 2020-07-01
12 Single's Day Gift Promo APAC 2020-11-11 2020-11-12
13 Chinese Moon Festival APAC 2020-10-01 2020-10-07
In [6]:
#Выведим данные таблицы final_ab_new_users
base_url = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?'
public_key = 'https://disk.yandex.ru/d/Qrto8Gerpu424g'

final_ab_new_users  = datasets (base_url, public_key)
final_ab_new_users
Out[6]:
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone
... ... ... ... ...
61728 1DB53B933257165D 2020-12-20 EU Android
61729 538643EB4527ED03 2020-12-20 EU Mac
61730 7ADEE837D5D8CBBD 2020-12-20 EU PC
61731 1C7D23927835213F 2020-12-20 EU iPhone
61732 8F04273BB2860229 2020-12-20 EU Android

61733 rows × 4 columns

In [7]:
# Выведим основную информацию о датафреймах с помощью метода `info()`,`head()`,`describe()`,`shape`
# с помощью цикла
for data in [final_ab_events, 
             ab_project_marketing_events, 
             final_ab_new_users, 
             final_ab_participants]:
    display(data.head(), 
            data.info(), 
            data.describe().T, 
            data.shape)
    display('****************************************************')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype  
---  ------      --------------   -----  
 0   user_id     440317 non-null  object 
 1   event_dt    440317 non-null  object 
 2   event_name  440317 non-null  object 
 3   details     62740 non-null   float64
dtypes: float64(1), object(3)
memory usage: 13.4+ MB
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99
None
count mean std min 25% 50% 75% max
details 62740.0 23.877631 72.180465 4.99 4.99 4.99 9.99 499.99
(440317, 4)
'****************************************************'
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   name       14 non-null     object
 1   regions    14 non-null     object
 2   start_dt   14 non-null     object
 3   finish_dt  14 non-null     object
dtypes: object(4)
memory usage: 576.0+ bytes
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
None
count unique top freq
name 14 14 Christmas&New Year Promo 1
regions 14 6 APAC 4
start_dt 14 14 2020-12-25 1
finish_dt 14 14 2021-01-03 1
(14, 4)
'****************************************************'
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   user_id     61733 non-null  object
 1   first_date  61733 non-null  object
 2   region      61733 non-null  object
 3   device      61733 non-null  object
dtypes: object(4)
memory usage: 1.9+ MB
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone
None
count unique top freq
user_id 61733 61733 D72A72121175D8BE 1
first_date 61733 17 2020-12-21 6290
region 61733 4 EU 46270
device 61733 4 Android 27520
(61733, 4)
'****************************************************'
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18268 entries, 0 to 18267
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   user_id  18268 non-null  object
 1   group    18268 non-null  object
 2   ab_test  18268 non-null  object
dtypes: object(3)
memory usage: 428.3+ KB
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test
None
count unique top freq
user_id 18268 16666 0FDFDA0B2DEC2D91 2
group 18268 2 A 9655
ab_test 18268 2 interface_eu_test 11567
(18268, 3)
'****************************************************'

Предобработка данных¶

Проверка типов данных¶

Изменим типы данных с датами на тип datetime

In [8]:
final_ab_events['event_dt']= pd.to_datetime(final_ab_events['event_dt'], format = '%Y-%m-%d')
ab_project_marketing_events ['start_dt']= pd.to_datetime(ab_project_marketing_events ['start_dt'], format = '%Y-%m-%d')
ab_project_marketing_events ['finish_dt']= pd.to_datetime(ab_project_marketing_events ['finish_dt'], format = '%Y-%m-%d')
final_ab_new_users['first_date']= pd.to_datetime(final_ab_new_users['first_date'], format = '%Y-%m-%d')

Проверим изменения

In [9]:
for data in [final_ab_events, 
             ab_project_marketing_events, 
             final_ab_new_users]:
    display(data.info())
    display('****************************************************')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype         
---  ------      --------------   -----         
 0   user_id     440317 non-null  object        
 1   event_dt    440317 non-null  datetime64[ns]
 2   event_name  440317 non-null  object        
 3   details     62740 non-null   float64       
dtypes: datetime64[ns](1), float64(1), object(2)
memory usage: 13.4+ MB
None
'****************************************************'
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype         
---  ------     --------------  -----         
 0   name       14 non-null     object        
 1   regions    14 non-null     object        
 2   start_dt   14 non-null     datetime64[ns]
 3   finish_dt  14 non-null     datetime64[ns]
dtypes: datetime64[ns](2), object(2)
memory usage: 576.0+ bytes
None
'****************************************************'
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   user_id     61733 non-null  object        
 1   first_date  61733 non-null  datetime64[ns]
 2   region      61733 non-null  object        
 3   device      61733 non-null  object        
dtypes: datetime64[ns](1), object(3)
memory usage: 1.9+ MB
None
'****************************************************'

Проверим дубликаты и пропуски¶

In [10]:
for data in [final_ab_events, 
             ab_project_marketing_events, 
             final_ab_new_users,
            final_ab_participants]:
    display(data.duplicated().sum(), data.isna().sum())
    display('****************************************************')
0
user_id            0
event_dt           0
event_name         0
details       377577
dtype: int64
'****************************************************'
0
name         0
regions      0
start_dt     0
finish_dt    0
dtype: int64
'****************************************************'
0
user_id       0
first_date    0
region        0
device        0
dtype: int64
'****************************************************'
0
user_id    0
group      0
ab_test    0
dtype: int64
'****************************************************'

Проверим датафрейм final_ab_events, где обаружены пропуски

In [11]:
display(final_ab_events['details'].value_counts())
display(final_ab_events['event_name'].value_counts())
4.99      46362
9.99       9530
99.99      5631
499.99     1217
Name: details, dtype: int64
login           189552
product_page    125563
purchase         62740
product_cart     62462
Name: event_name, dtype: int64

**Наблюдение:** Столбец delailsсодержит 377 577 пропусков, заполним их пропусками, тк это доп данные по событию purchase.

In [12]:
final_ab_events['details'] = final_ab_events['details'].fillna(0)
In [13]:
# Проверим
final_ab_events.isna().sum()
Out[13]:
user_id       0
event_dt      0
event_name    0
details       0
dtype: int64

Анализ данных (EDA)¶

**Техническое задание**

  • Название теста: recommender_system_test;
  • группы: А — контрольная, B — новая платёжная воронка;
  • дата запуска: 2020-12-07;
  • дата остановки набора новых пользователей: 2020-12-21;
  • дата остановки: 2021-01-04;
  • аудитория: 15% новых пользователей из региона EU;
  • назначение теста: тестирование изменений, связанных с внедрением улучшенной рекомендательной системы;
  • ожидаемое количество участников теста: 6000.
  • ожидаемый эффект: за 14 дней с момента регистрации пользователи покажут улучшение каждой метрики не менее, чем на 10%:
    • конверсии в просмотр карточек товаров — событие product_page,
    • просмотры корзины — product_cart,
    • покупки — purchase.

Во времея проведения EDA нам нужно ответить на следующие вопросы:

  • Одинаково ли распределено количество событий на каждого пользователя в выборке?
  • Как распределено количество событий по дням?
  • Встречаются ли в разных выборках одни и теже пользователи?
  • Как меняется корверсия в воронке на разных этапах?
  • Что нужно учесть перед началом А/В- тестирования?

Оценка корректности проведения теста¶

Проверим, что время проведения теста не совпадает с маркетинговыми и другими активностями из таблицы ab_project_marketing_events

In [14]:
ab_project_marketing_events[np.logical_and(ab_project_marketing_events['start_dt'] > '2020-12-07', \
                                           ab_project_marketing_events['finish_dt'] < '2021-04-01')]
Out[14]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07

**Наблюдение:** В период проведения теста проводились маркетинговые активности Christmas&New Year Promo и CIS New Year Gift Lottery .

In [15]:
#Посмотрим распределение пользователей по тестам и по группам
final_ab_participants.groupby(['ab_test', 'group']).agg({'user_id': 'nunique'}).reset_index()
Out[15]:
ab_test group user_id
0 interface_eu_test A 5831
1 interface_eu_test B 5736
2 recommender_system_test A 3824
3 recommender_system_test B 2877

В тесте recommender_system_test имееется две группы. В группу А попали 3824 пользователя, а в группу В 2877.

In [16]:
#проверим соответствие дат регистрации техническому заданию
print('Минимальная дата:',  final_ab_new_users['first_date'].min())
print('Максимальная дата:', final_ab_new_users['first_date'].max())
Минимальная дата: 2020-12-07 00:00:00
Максимальная дата: 2020-12-23 00:00:00

В ТЗ указано, что дата остановки набора новых пользователей: 2020-12-21. А в наших данных, как мы видим, есть регистрации и 23.12.2020. Удалим лишние записи.

In [17]:
# Создадим датафрэйм только у теми пользователеми, которые были зарегистрованы на тест в это промежуток

final_ab_new_users = final_ab_new_users.query ('first_date <="2020-12-21"')
final_ab_new_users['first_date'].max()
Out[17]:
Timestamp('2020-12-21 00:00:00')

Дата остановки теста: 2021-01-04, согласно ТЗ. Проверим на корректность даты в таблице с событиями

In [18]:
print('Минимальная дата:',  final_ab_events['event_dt'].min())
print('Максимальная дата:',  final_ab_events['event_dt'].max())
Минимальная дата: 2020-12-07 00:00:33
Максимальная дата: 2020-12-30 23:36:33

Последняя дата событий в данных 30 декабря. Хотя тест должен был продолжаться до 4 января. Возможно тест завершили раньше.

Пользователи, зарегистрировавшиеся после 16 декабря не "прошли" весь лайфтайм в 14 дней, и не в полной мере показали как внесенные изменения повлияли на их активность. Это может сильно исказить результаты тестирования.

In [19]:
print('Количество новых пользователей', final_ab_new_users['first_date'].count())
print('Количество пользователей, участвующих в тесте', final_ab_participants['user_id'].nunique())
Количество новых пользователей 56470
Количество пользователей, участвующих в тесте 16666

Посмотрим количество аудитории: 15% новых пользователей из региона EU по датафрейму, где только с период с 07 по 21.12.2020

In [20]:
# Объеденим таблицы методом merge по user_id
users = final_ab_participants.merge(final_ab_new_users, on = 'user_id', how = 'left').dropna()
users.shape
Out[20]:
(17266, 6)
In [21]:
print('Пользователи из Европы, принявшие участие в тесте',\
      len(users.query('ab_test == "recommender_system_test" and region == "EU"')))
print('Всего зарегистрировалось пользователей из Европы',len(final_ab_new_users.query('region == "EU"')))
print('Процент пользователей из Европы, принявших участие в тесте', len(users.query('ab_test == "recommender_system_test" and region == "EU"'))\
      /len(final_ab_new_users.query('region == "EU"'))*100)
Пользователи из Европы, принявшие участие в тесте 6351
Всего зарегистрировалось пользователей из Европы 42340
Процент пользователей из Европы, принявших участие в тесте 15.0

Данные соответствуют ТЗ, тк доля пользователей из Европы 15%

Проверим правильность распределения участников теста

Согласно таблице final_ab_participants параллельно проводилось два теста, наша задача исключить задвоение участников, которые были в двух тестах.

In [22]:
# Сгруппируем данные по пользователю и группе

dupl_user = users.groupby('user_id', as_index=False).agg({'ab_test': 'nunique'})
#dupl_user.columns = ['user_id', 'group', 'count']
# Сделаем сред по количеству 
dupl_user = dupl_user.query('ab_test > 1').sort_values(by='user_id')
print('Количество пользователей, которые участвовали в двух тестах {} или {:.1%}'
      .format(dupl_user['user_id'].count(), dupl_user['user_id'].count()/final_ab_participants['user_id'].count()))
Количество пользователей, которые участвовали в двух тестах 1602 или 8.8%
In [23]:
dupl_user.head(3)
Out[23]:
user_id ab_test
2 001064FEAAB631A1 2
10 00341D8401F0F665 2
12 003B6786B4FF5B03 2
In [24]:
# Перепроверим любого пользователя на участие в друх выборках
display(final_ab_participants.query('user_id == "001064FEAAB631A1"'))
display(final_ab_participants.query('user_id == "003B6786B4FF5B03"'))
user_id group ab_test
235 001064FEAAB631A1 B recommender_system_test
17892 001064FEAAB631A1 B interface_eu_test
user_id group ab_test
3156 003B6786B4FF5B03 A recommender_system_test
8143 003B6786B4FF5B03 A interface_eu_test

Пользователей, принявших участие в двух тестах одновременно 1602 или 8,8%. Для чистоты эксперемента необходимо удалить этих пользователей, так как при дальнейшем анализе они могут исказить конечный результат. Ведь мы точно не можем сказать, какой из экспериментов повлиял на принятие того или иного решения пользователя.

In [25]:
print('Всего пользователей БЫЛО', users['user_id'].nunique())

print(users.groupby('ab_test').agg({'user_id': 'nunique'}))
Всего пользователей БЫЛО 15664
                         user_id
ab_test                         
interface_eu_test          10565
recommender_system_test     6701
In [26]:
#удаляем дубликаты
list = dupl_user['user_id']
users = users.query('user_id not in @list')
In [27]:
print('Всего пользователей СТАЛО', users['user_id'].nunique())

print(users.groupby('ab_test').agg({'user_id': 'nunique'}))
Всего пользователей СТАЛО 14062
                         user_id
ab_test                         
interface_eu_test           8963
recommender_system_test     5099

В итоге после удаления у нас осталось всего 14062 уникальных пользователя, из которых 5099 попали в recommender_system_test. Для дальнейшей работы выделим данные по тесту recommender_system_test в отдельный датафрейм и проверим на пересечение пользователей в группах.

In [28]:
#оставляем данные о тесте recommender_system_test
recommender_system_test = users.query('ab_test == "recommender_system_test"')
recommender_system_test['ab_test'].unique()
recommender_system_test.sample(5)
Out[28]:
user_id group ab_test first_date region device
1808 7B646640B70CCFF4 A recommender_system_test 2020-12-12 EU Android
5061 E659A40ADD2D4E31 B recommender_system_test 2020-12-21 EU PC
1554 6F1E79FE8F5795CF B recommender_system_test 2020-12-14 EU Android
2151 E53E3F40AE9433F9 B recommender_system_test 2020-12-19 EU Android
2726 4F8F5E43B8BD003D A recommender_system_test 2020-12-12 EU Android
In [29]:
#смотрим распределение по группам
recommender_system_test.groupby('group').agg({'user_id': 'nunique'}).reset_index()
Out[29]:
group user_id
0 A 2903
1 B 2196
In [30]:
# Сгруппируем данные по пользователю и группе

dupl_group = recommender_system_test.groupby('user_id', as_index=False).agg({'ab_test': 'nunique'}).reset_index()
dupl_group.columns = ['user_id', 'group', 'count']
# Сделаем сред по количеству 
dupl_group = dupl_group.query('count > 1').sort_values(by='user_id')
print('Количество пользователей, которые участвовали в двух группах {} или {:.1%}'
      .format(dupl_group['user_id'].count(), dupl_group['user_id'].count()/final_ab_participants['user_id'].count()))
Количество пользователей, которые участвовали в двух группах 0 или 0.0%

Пересечений пользователей по группам нет.

Посмотрим равномерное распределение по группам

In [31]:
recommender_system_test['group'].value_counts()
Out[31]:
A    2903
B    2196
Name: group, dtype: int64

Группы распределены не одинаково. В группе А присутствует 2903 пользователей (57%), а в группе В - 2196 пользователей (43%)

Посмотрим на горизонт проведения теста в 14 дней и удалим не нужное

Согласно нашему ТЗ наш тест происходил в промежуток с 2020-12-07 по 2020-12-21, присоеденим данные о событиях пользователей к имеющемуся тесту

In [32]:
#присоединяем события
user_event = recommender_system_test.merge(final_ab_events, on = 'user_id', how = 'left')
display(user_event.head(), user_event.info())
<class 'pandas.core.frame.DataFrame'>
Int64Index: 21115 entries, 0 to 21114
Data columns (total 9 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   user_id     21115 non-null  object        
 1   group       21115 non-null  object        
 2   ab_test     21115 non-null  object        
 3   first_date  21115 non-null  datetime64[ns]
 4   region      21115 non-null  object        
 5   device      21115 non-null  object        
 6   event_dt    18804 non-null  datetime64[ns]
 7   event_name  18804 non-null  object        
 8   details     18804 non-null  float64       
dtypes: datetime64[ns](2), float64(1), object(6)
memory usage: 1.6+ MB
user_id group ab_test first_date region device event_dt event_name details
0 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:27 purchase 99.99
1 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-25 00:04:56 purchase 4.99
2 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:29 product_cart 0.00
3 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-25 00:04:57 product_cart 0.00
4 D1ABA3E2887B6A73 A recommender_system_test 2020-12-07 EU PC 2020-12-07 14:43:27 product_page 0.00
None
In [33]:
#удалим пользователей без событий, они нам без надобности
df_AB_testing = user_event.dropna(subset = ['event_name'])
len(user_event)
Out[33]:
21115
In [34]:
# удаляем события, совершенные после лайфтайма
df_AB_testing = (df_AB_testing.drop(df_AB_testing[df_AB_testing['event_dt'] >= (df_AB_testing['first_date'] + pd.Timedelta(14, 'D'))].index))
df_AB_testing.shape
Out[34]:
(18154, 9)

После того, как мы нашли участников тестирования и зарегистрированных участников в нужный промежуток времени, мы объединили таблицы, удалили пользователей без событий и удалили события, которые не вошли в указанный промежуток.

Можно приступить к основному анализу данных.

Распределение количества событий на пользователя в выборках¶

Посмотрим, сколько уникальных пользователей входит в каждую группу.

In [35]:
AB_testing_unique_users = df_AB_testing.groupby('group')['user_id'].agg(['count']).reset_index()
AB_testing_unique_users['%'] = ((AB_testing_unique_users['count']/AB_testing_unique_users['count'].sum())*100).round(1)
AB_testing_unique_users.sort_values(by='%', ascending=False)
AB_testing_unique_users.style.bar(subset=['%'], color='#ffe135')
Out[35]:
  group count %
0 A 14340 79.000000
1 B 3814 21.000000

Посмотрим на гистограмме распределение количества событий на одного пользователя в каждой группе

In [36]:
# Сгруппируем данные
events_users_count = df_AB_testing.groupby(['user_id', 'group'], as_index=False).agg({'event_name' : 'count'})
events_users_count.columns = ['user_id', 'group', 'events']
In [37]:
events_users_count.query('group == ["A","B"] & events > 0')\
                                .groupby('group')['events']\
                                .plot(kind='hist', bins=len(events_users_count['events'].unique()), alpha=0.5,\
                                      figsize=(15,8))
plt.legend(['"группа "А"','"группа "B"'])
plt.xlabel('Количество событий')
plt.ylabel('Количество пользователей')
plt.title('Гистограмма распределения количества событий на одного пользователя')
plt.show()

Посмотрим, как происходило распределение событий на одного пользователя в выборках

In [38]:
print('В среднем на пользователя приходится %d событий' % (df_AB_testing['user_id'].value_counts().mean()))
print('По медиане на пользователя приходится %d событий' % (df_AB_testing['user_id'].value_counts().median()))
print('По моде на пользователя приходится %d событий' % (df_AB_testing['user_id'].value_counts().mode()))
В среднем на пользователя приходится 6 событий
По медиане на пользователя приходится 6 событий
По моде на пользователя приходится 6 событий

В группе В на одного пользователя чаще всего приходится от 1 до 6 событий. В группе А чаще совершают событий от 2 до 12.

In [39]:
#смотрим описательную статистику распределения событий в группе А
df_AB_testing.query('group == "A"').groupby('user_id')['event_name'].count().describe()
Out[39]:
count    2082.000000
mean        6.887608
std         3.824835
min         1.000000
25%         4.000000
50%         6.000000
75%         9.000000
max        24.000000
Name: event_name, dtype: float64
In [40]:
#смотрим описательную статистику распределения событий в группе B
df_AB_testing.query('group == "B"').groupby('user_id')['event_name'].count().describe()
Out[40]:
count    706.000000
mean       5.402266
std        3.256364
min        1.000000
25%        3.000000
50%        4.000000
75%        6.000000
max       24.000000
Name: event_name, dtype: float64
In [41]:
#распределение количества участников по группам
df_AB_testing.groupby('group')['user_id'].nunique()
Out[41]:
group
A    2082
B     706
Name: user_id, dtype: int64

ВЫВОД:

После изучения данных, предобработки данных, соединение полученных таблиц в единую таблицу `df_AB_testing` мы видим следующее: В группе А на одного пользователя в среднем приходится 7 событий. В группе В пользователь совершает в среднем 5 событий. **Разница в среднем количестве событий между группами может быть обусловленна:
- неравномерным распределением количества пользователей по группам
- на активность пользователей из группы В могли повлиять внесенные изменения в рамках теста
**

Распределение количество событий по дням¶

Посчитаем, как число событий распределено по дням. Сгруппируем таблицу df_AB_testing по дню события и группе A/B-теста, посчитаем количество пользователей. Сохраним результат в переменную df_AB_testing_daily.

In [42]:
# Добавим в датафрэйм столбец с датой
df_AB_testing['event_day'] = df_AB_testing['event_dt'].dt.date
df_AB_testing.sample(5)
Out[42]:
user_id group ab_test first_date region device event_dt event_name details event_day
4597 D493E891D8F64BBC A recommender_system_test 2020-12-14 EU Android 2020-12-22 02:30:08 login 0.0 2020-12-22
7947 0DA942E5D1BB9A1F A recommender_system_test 2020-12-16 EU Mac 2020-12-22 06:48:37 product_page 0.0 2020-12-22
15867 8CB19D3BF05472EB A recommender_system_test 2020-12-19 EU Mac 2020-12-19 21:22:07 login 0.0 2020-12-19
18823 9073CEFFB95784BA A recommender_system_test 2020-12-08 EU Mac 2020-12-09 19:01:05 login 0.0 2020-12-09
5124 796235FF6E88D357 A recommender_system_test 2020-12-07 EU Android 2020-12-18 16:34:45 login 0.0 2020-12-18
In [43]:
# Посчитаем кол-во событий по дням
df_AB_testing['event_day'].value_counts()
Out[43]:
2020-12-21    1854
2020-12-20    1442
2020-12-19    1408
2020-12-18    1189
2020-12-22    1137
2020-12-17    1136
2020-12-16    1101
2020-12-14    1040
2020-12-15    1008
2020-12-23     867
2020-12-24     723
2020-12-09     569
2020-12-25     540
2020-12-07     524
2020-12-27     468
2020-12-10     461
2020-12-26     461
2020-12-08     450
2020-12-12     436
2020-12-11     404
2020-12-13     368
2020-12-28     312
2020-12-29     256
Name: event_day, dtype: int64
In [44]:
df_AB_testing_daily = df_AB_testing.groupby(['event_day', 'group'])\
                                   .agg({'event_name':'count'})\
                                   .reset_index()
df_AB_testing_daily.head(30)
Out[44]:
event_day group event_name
0 2020-12-07 A 234
1 2020-12-07 B 290
2 2020-12-08 A 267
3 2020-12-08 B 183
4 2020-12-09 A 296
5 2020-12-09 B 273
6 2020-12-10 A 265
7 2020-12-10 B 196
8 2020-12-11 A 282
9 2020-12-11 B 122
10 2020-12-12 A 282
11 2020-12-12 B 154
12 2020-12-13 A 253
13 2020-12-13 B 115
14 2020-12-14 A 823
15 2020-12-14 B 217
16 2020-12-15 A 834
17 2020-12-15 B 174
18 2020-12-16 A 810
19 2020-12-16 B 291
20 2020-12-17 A 927
21 2020-12-17 B 209
22 2020-12-18 A 976
23 2020-12-18 B 213
24 2020-12-19 A 1180
25 2020-12-19 B 228
26 2020-12-20 A 1193
27 2020-12-20 B 249
28 2020-12-21 A 1537
29 2020-12-21 B 317
In [45]:
# Построим график

fig = px.bar(df_AB_testing_daily, x="event_day", y= 'event_name', color="group", title="Распределение событий по группам в разрезе дней",\
             #text_auto=True, 
             pattern_shape_sequence=["+"],\
             labels= {'event_day': 'Дата', 'event_name': 'Количество событий'},
             width=1000, # указываем размеры графика
             height=500)

fig.show()

**Наблюдение:** Основная активность двух групп в разрезе событий пришлась на 21 декабря 2020 года. В этот день обе группы имеют максимальные значения.

Посмотрим отдельно распределение каждого события в каждой группе по дням

In [46]:
# Сделаем срез по группам

group_A = df_AB_testing.query('group == "A"')
group_B = df_AB_testing.query('group == "B"')
In [47]:
# Сгруппируем события группы "А" по дням 
group_A.pivot_table(index='event_day', values='user_id',columns='event_name',aggfunc='count')\
       .plot.bar(stacked=True,figsize=(12,6))
plt.title("Группа A")
plt.show()
In [48]:
# Сгруппируем события группы "B" по дням
group_B.pivot_table(index='event_day', values='user_id',columns='event_name',aggfunc='count')\
       .plot.bar(stacked=True,figsize=(12,6))
plt.title("Группа B")
plt.show()

ВЫВОД:

В группе А рост активности начинается с 14 декабря. Пик активности приходится на 21 декабря, в этот день было совершено около 1400 событий. Затем активность идёт на спад. В группе В таких резких всплесков не наблюдается, там события распределны более равномерно. Самое большое число событий в этой группе так же пришлось на 21 декабря и достигало 300 событий. ** Резкое увеличение событий в группе А 14.12.20 вызвано большим количеством новых регистрации в это день. На графике количества регистраций по дням, мы видели пик наплыва новых пользователей в этот день - почти 300 человек. **

Изменение конверсии воронки в выборках на разных этапах¶

Этапы воронки продаж

1. Login - пользователь входит на сайт;\ 2. Product_page - предложение о товаре (экран с товаром);\ 3. Product_card - переход в корзину;\ 4. Purshase - экран успешной оплаты заказа.\ Этапы Product_card и Purshase имеют разное количество заходов пользователей, причем покупок больще чем переходов в корзину. Следовательно покупки совершаются по одному товару миную корзину. Надо расставить события в нужный порядок.

Посчитаем общее количество событий по всем этапам

In [49]:
# Сгруппируем данные по событиям
steps = df_AB_testing.groupby('event_name').agg({'event_name': ['count'], 'user_id': ['nunique'] }).reset_index()
steps.columns = ('event_name', "count", 'users')

# добавим столбец с конверсией
start= steps.loc[0,'users']
steps['Конверсия'] = round(steps['users']/start*100, 1)
# Посчитаем конверсию от этапа к этапу
steps['Конверсия в шаг %'] = round(steps['users']/steps['users'].shift()*100, 1)
#добавим вручную первое значение stage_conversion
steps.loc[0, 'Конверсия в шаг %'] = round(steps.loc[0,'users']/start*100, 1)
steps = steps.sort_values(by='count', ascending=False)
steps
Out[49]:
event_name count users Конверсия Конверсия в шаг %
0 login 8194 2787 100.0 100.0
2 product_page 5108 1757 63.0 212.7
1 product_cart 2446 826 29.6 29.6
3 purchase 2406 850 30.5 48.4
In [50]:
#построим воронку с процентом перехода на каждый этап относительно начального 
fig = go.Figure(go.Funnel(
    x = steps['count'], 
    y = steps['event_name'],
    textinfo = "value+percent previous+percent initial"))
fig.update_layout(title='Этапы воронки продаж', title_x = 0.55)
fig.show();

**Наблюдение:**

По итогам анализа воронки продаж было выявлено следующее:

  • Конверсия (расчет с первого шага):

    • зарегистрованных пользователей мы взяли за 100%
      • предложение о товара 63% пользователей
        • перешли в корзину 29,6%
          • оплата заказа 30,5%
  • При переходе с первого этапа на второй теряется 37.3% пользователей. Переходит 62.7% пользователей.
  • При переходе со второго этапа на третий теряется 53.1% пользователей. Переходит 46.9% пользователей.
  • При переходе с третьего на четвертый этап увеличивается количество на 4.5% пользователей. Дошло до оплаты 51.4% пользователей.

Создадим сводную таблицу conver_group, сгруппируем данные по группам A/B-теста, в качестве столбцов — названия события, посчитаем количество пользователей и выведем на экран.

In [51]:
conver_group = df_AB_testing.pivot_table(index='event_name', columns='group', values='user_id', aggfunc='nunique').reset_index()
conver_group= conver_group.replace({'event_name':{'login':'1_регистрация', 'product_page':'2_просмотр карточек товара',\
                                                     'product_cart':'3_просмотр корзины','purchase':'4_покупка'}})
conver_group = conver_group.sort_values(by='event_name')
conver_group
Out[51]:
group event_name A B
0 1_регистрация 2082 705
2 2_просмотр карточек товара 1360 397
1 3_просмотр корзины 631 195
3 4_покупка 652 198

Посмотрим изменение воронки продаж по каждой группе

In [52]:
funnel_A = (group_A.groupby('event_name')['user_id'].nunique().sort_values(ascending=False).to_frame().reset_index()
              .rename(columns={'user_id': 'total_users'}))
funnel_A['step'] = pd.Series([0, 1, 3, 2])
funnel_A = funnel_A.sort_values(by='step', ascending=True)


funnel_B = (group_B.groupby('event_name')['user_id'].nunique().sort_values(ascending=False).to_frame().reset_index()
              .rename(columns={'user_id': 'total_users'}))
funnel_B['step'] = pd.Series([0, 1, 3, 2])
funnel_B = funnel_B.sort_values(by='step', ascending=True)
funnel_B
Out[52]:
event_name total_users step
0 login 705 0
1 product_page 397 1
3 product_cart 195 2
2 purchase 198 3
In [53]:
#Построим Stacked Funnel Plot with go.Funnel

fig = go.Figure()
fig.add_trace(go.Funnel(
    name = 'group_A',
    y = funnel_A['event_name'],
    x = funnel_A['total_users'],
    textinfo = "value+percent initial"))
fig.update_layout(title='Воронка продаж группы "А" и группы "В"', title_x = 0.55)

fig.add_trace(go.Funnel(
    name = 'group_B',
    orientation = "h",
    y = funnel_B['event_name'],
    x = funnel_B['total_users'],
    textposition = "inside",
    textinfo = "value+percent initial"))

fig.show()

**Наблюдение:**

По графику видно, что в группе А конверсия по этапам чуть лучше, чем в группе В. После авторизации в группе А лишь 31% доходит до покупок, в группе В - 28%. Больше всего пользователей отваливается на этапе просмотра карточек товара, к следующему этапу переходит не более 50%. Показатель прехода от 3 этапа к 4 очень позитивный - после просмотра корзины все пользователи переходят к покупкам. Можно заметить,что на 4 этапе в обеих группах процент пользователей больше, чем на предыдущем этапе. Это может говорить о том, что на платформе нестрогая воронка продаж и можно приобрести продукт минуя некоторые этапы.
Согласно ТЗ, Ожидаемый эффект: за 14 дней с момента регистрации в системе пользователи покажут улучшение каждой метрики не менее, чем на 10%. Что же мы имеем по факту:

  • Процент перехода от первого этапа ко второму в контрольной группе составил 65%, в эксперементальной - 56%. Разница в 18% с отрицательных покаазателем.
  • Процент перехода от второго этапа к третьему в контрольной группе 46%, в эксперементальной 49%. Тут динамика положительна, увеличение на 3%
  • Процент перехода с третьего этапа к покупке составил 103% в контрольной и 101% в эксперементальной группах.

Как мы видим, ожидаемый эффект не оправдался.

Особенности данных перед А/В-тестирование¶

Перед А/В тестом проводят A/A-тест. Если трафик и инструмент проведения A/A-теста не подвели, различий в показателях не будет и поможет определить длительность теста и методику анализа данных. Критерии успешного A/A-теста:

  • если деление трафика, не более чем на 1% отличается

Итого: Контрольная группа почти в 3 раза превышает эксперементальную.

  • достаточное количество участников в выборке

Итого: Согласно ТЗ, предполагалась, что аудитория теста будет сотоять из 15% новых пользователей региона EU. По факту же, в эксперимент попали пользователи из других регионов, пусть и в незначительном количестве, но это тоже может исказить результат эксперимента.

  • различие ключевых метрик по группам не превышает 1% и не имеет статистической значимости;
  • выброны корректные промежутки времени проведения теста без параллельного вмешательства других активностей в период проведения теста.

Итого: Изначально предполагалось, что дата окончания эксперимента 2021-01-04. Но по событиям пользователей мы видим, что фактически эксперемент заканчивается 29 декабря- последняя дата наблюдаемой активности пользователей. Время проведения эксперимента выбрано неудачно, так как это новогодние праздники и предновогодняя суета могла повлиять на активность пользователей в этот период. На время проведения теста накладывается маркетинговая кампания, проводимая с 25 декабря

Перед тем как начать A/B-тест, нужно убедиться, что:

  • на результаты не влияют аномалии и выбросы в генеральной совокупности;
  • что в тесте неслишком большое количество групп, что чаще приводит к ложнопозитивному результату;
  • измеритель качества «деления» трафика подтвердил однородность трафика и работает корректно.

Оценка результатов А/В-тестирования¶

Результаты А/В-тестирования¶

Перед началом проведения А/В теста вероятно всего не проводился А/А тест для оценки корректного времени и необходимого количества данных. К сожалению, в данном примере мы столкнулись с проблемой некорректного деления трафика теста, те пользователи были распределены меджу группами неравномерно, что привело к искажению результатов.

Проверка статистической разницы долей z-критериев¶

1. Проверим по группам

Проверим гипотезу о равенстве долей при помощи Z-критерия. Для этого напишем функцию.

Посчитаем статистическую значимость различия между группами.

Формулировка гипотез:

  • Нулевая гипотеза(H_0): группа "А" = группа "В" - одинаковые
  • Альтернативная гипотеза (H_a): группа "А" = группа "В" -" разные

alpha = 0.05 - выберим данный уровень значимости (вероятный порог "необычности")

In [54]:
user_event = df_AB_testing.pivot_table(index = 'group', columns = 'event_name', values = 'user_id',
                                       aggfunc = 'nunique').reset_index()
user_events = user_event.set_index(user_event.columns[0])
user_events 
Out[54]:
event_name login product_cart product_page purchase
group
A 2082 631 1360 652
B 705 195 397 198
In [55]:
# Напишем функцию

def st_test(pA_ev, pB_ev, pA_us, pB_us): 
    # пропорция успехов в первой группе:
    pA = pA_ev / pA_us
    # пропорция успехов во второй группе:
    pB = pB_ev / pB_us 
    # пропорция успехов в комбинированном датасете:
    p_combined = (pA_ev + pB_ev) / (pA_us + pB_us)
    # разница пропорций в датасетах
    difference = pA - pB
    z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1 / pA_us + 1 / pB_us))
    # задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
    distr = st.norm(0, 1)
    p_value = (1 - distr.cdf(abs(z_value))) * 2

    return p_value
In [56]:
# Укажем значения по группам А и В в p-value
p_value = st_test(2082, 705, user_events.loc['A'].sum(), user_events.loc['B'].sum())
print('p-значение: ',"{0:.3f}".format(p_value))

alpha = .05 
if (p_value < alpha):
    print("Отвергаем нулевую гипотезу: между долями есть значимая разница")
else:
    print("Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными")
p-значение:  0.036
Отвергаем нулевую гипотезу: между долями есть значимая разница

**Наблюдение:**

Получив крайне маленькое значение p-value, мы отвергли Нулевую гипотезу. Таким образом, у нас практически нет вероятности получить одинаковые доли групп "А" и "В".

2. Проверим по каждому событию

In [57]:
total_users = df_AB_testing.groupby('group')['user_id'].nunique()
total_users
Out[57]:
group
A    2082
B     706
Name: user_id, dtype: int64

Событие login совершили все пользователи каждой группы, поэтому проводить сравнение на данном этапе бессмыслено. Удалим эту строку из таблицы.

In [58]:
conver_group = conver_group.drop(0, 0).reset_index(drop=True)
conver_group
Out[58]:
group event_name A B
0 2_просмотр карточек товара 1360 397
1 3_просмотр корзины 631 195
2 4_покупка 652 198

При написании функции применим поправку Бонферрони, чтобы смягчить увеличение вероятности совершения ошибки 1-го рода при многократной проверке

In [59]:
#напишем функцию для проведения А/В теста. В качестве аргумента функции будем передовать названия сравниваемых групп
def stat_test(group1, group2):
    for i in conver_group.index:
        alpha = .05/len(conver_group) # критический уровень статистической значимости c поправкой Бонферрони


        successes = np.array([conver_group[group1][i],conver_group[group2][i]])
        trials = np.array([total_users[group1], total_users[group2]])

# пропорция успехов в первой группе:
        p1 = successes[0]/trials[0]

# пропорция успехов во второй группе:
        p2 = successes[1]/trials[1]

# пропорция успехов в комбинированном датасете:
        p_combined = (successes[0] + successes[1]) / (trials[0] + trials[1])

# разница пропорций в датасетах
        difference = p1 - p2 

# считаем статистику в ст.отклонениях стандартного нормального распределения
        z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/trials[0] + 1/trials[1]))

# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
        distr = st.norm(0, 1)  

        p_value = (1 - distr.cdf(abs(z_value))) * 2
        print('Событие: ', conver_group['event_name'][i])
        print('p-значение: ', p_value)

        if p_value < alpha:
            print('Отвергаем нулевую гипотезу: между долями есть значимая разница')
            print()
        else:
            print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными')
            print()
In [60]:
stat_test('A', 'B')
Событие:  2_просмотр карточек товара
p-значение:  1.5371909704686715e-05
Отвергаем нулевую гипотезу: между долями есть значимая разница

Событие:  3_просмотр корзины
p-значение:  0.1766337419130104
Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными

Событие:  4_покупка
p-значение:  0.10281767567786759
Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными

**Наблюдение:**

По результатам А/В теста значимая разница между группами прослеживается только на этапе просмотра карточек товара. А вот что касается перехода в корзину и покупки товара, то тут значимой разницы не наблюдается. Нет оснований считать, что есть разница в конверсии между контрольной и экспериментальной группой после внедрения новшеств на сайте.

Общий вывод по этапу исследовательского анализа данных и по проведённой оценке результатов A/B-тестирования. Заключение о корректности проведения теста¶

ВЫВОД:

После того, как изучили датасеты, выявили следующее: на этапе изучения данных * Типы данных в датах были изменены. * Дубликатов не было. * Пустые значения в столбце `details` были заменены на нули. * После проведения среза по виду теста и периоду проведения было обнаружено 1602 человека или 8,8%, которые учавствовали в двух выборках. * Количесво строк таблицы: 24698, данные после обработки уменьшилось на 5.61% Количество уникальных участников тестирования: 3675, данные после обработки уменьшилось на 61.25% * Внутри исследуемого теста пересечений пользоветеле между группами не выявлено * На одного пользователя приходилось в среднем 6 событий и распределение событий в каждой группе на каждого пользователя одинаковое (группа А-7 событий, группа В- 5 событий). * Активность пользователей на сайте прекращается после 30.12, хотя тест продолжался до 4 января. * В группе А рост активности начинается с 14 декабря. Пик активности приходится на 21 декабря, далее идут спад. В группе В таких резких всплесков не наблюдается, там события распределны равномерно с начала эксперимента до 21 декабря. Далее тоже наблюдается спад активности. на этапе изучения воронки * События распределились следующим образом: 1.login - авторизация на сайте магазина 2.product_page - просмотр карточек товаров 3.product_cart - переход в корзину 4.purchase - покупка товара * Процент перехода от первого этапа ко второму в контрольной группе составил 65%, в эксперементальной - 56%. Разница в 18% с отрицательных покаазателем. * Процент перехода от второго этапа к третьему в контрольной группе 46%, в эксперементальной 49%. Тут динамика положительна, увеличение на 3% * Процент перехода с третьего этапа к покупке составил 103% в контрольной и 101% в эксперементальной группах. * По результатам заметно, что некоторые пользователи делают заказ, минуя шаг "просмотр корзины". Вероятнее всего, на сайте можно купить товар сразу, не добавляя его в корзину. в ходе проведения А/В тестирования * Выявлено значительное расхождение между двумы группами * Разницы в поведении между контрольными и эксперементальной группой на этапе перехода в корзину и покупки товара не выявлено. Внедренные изменения никак не повляли на поведение пользователей. * Cтоит обратить внимание на особенности в данных, которые могли негативно повлиять на результат проводимого теста: 1. Контрольная группа почти в 3 раза превышает эксперементальную. 2. В эксперимент попали пользователи из других регионов, участие которых не предполагалось в данном тестировании 3. Фактически, эксперимент закончился раньше, чем предпологалось изначально 4. Во время проведения эксперимента проводилась маркетинговая кампания, которая могла повлиять на активность пользователей. Рекомендация: Стоит не принимать результаты А/В тестирования, тк они искажены.Предлагаю провести тест повторно в период, не захватывающий праздники, и не влиящий на поведение пользователей.